package org.lc.stk.security.jwt;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.SecretKey;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;

/**
 * JWT(JSON Web Token)工具类
 *
 * <p>该工具类提供了JWT令牌的完整生命周期管理，包括生成、解析和验证。JWT令牌由三部分组成：</p>
 *
 * <h3>1. JWT结构</h3>
 * <ul>
 *   <li>Header(头部): 指定加密算法和令牌类型</li>
 *   <li>Payload(载荷): 包含声明(Claims)信息，如：
 *     <ul>
 *       <li>用户名(subject)</li>
 *       <li>签发时间(issuedAt)</li>
 *       <li>过期时间(expiration)</li>
 *       <li>其他自定义信息</li>
 *     </ul>
 *   </li>
 *   <li>Signature(签名): 使用密钥对头部和载荷进行签名</li>
 * </ul>
 *
 * <h3>2. 安全考虑</h3>
 * <ul>
 *   <li>使用HMAC-SHA256算法进行签名</li>
 *   <li>令牌包含过期时间，超时自动失效</li>
 *   <li>密钥通过配置注入，支持定期轮换</li>
 *   <li>敏感信息不应放在载荷中</li>
 * </ul>
 *
 * <h3>3. 异常处理</h3>
 * <ul>
 *   <li>签名无效: 返回null或抛出异常</li>
 *   <li>令牌过期: 通过{@link #isTokenExpired}方法检测</li>
 *   <li>格式错误: 解析时返回null或抛出异常</li>
 * </ul>
 *
 * <p>使用示例:</p>
 * <pre>
 * // 生成令牌
 * String token = jwtUtil.generateToken(userDetails);
 *
 * // 验证令牌
 * if (jwtUtil.validateToken(token, userDetails)) {
 *     // 令牌有效，处理请求
 * } else {
 *     // 令牌无效，拒绝请求
 * }
 * </pre>
 *
 * @see JwtProperties 配置属性类
 * @see io.jsonwebtoken.Jwts JWT库核心类
 */
@Component
public class JwtUtil {

    /**
     * JWT属性配置。
     */
    private final JwtProperties jwtProperties;

    /**
     * 用于签名JWT的密钥。
     */
    private final SecretKey secretKey;

    /**
     * 构造方法，初始化JWT属性和密钥。
     *
     * @param jwtProperties JWT属性配置
     */
    public JwtUtil(JwtProperties jwtProperties) {
        this.jwtProperties = jwtProperties;
        this.secretKey = Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes());
    }

    /**
     * 为用户生成JWT令牌
     *
     * <p>根据用户详细信息生成一个新的JWT令牌。该令牌包含:</p>
     * <ul>
     *   <li>用户名作为subject</li>
     *   <li>签发时间(当前时间)</li>
     *   <li>过期时间(当前时间+配置的过期时间)</li>
     *   <li>自定义声明(可扩展)</li>
     * </ul>
     *
     * <p><b>安全注意:</b></p>
     * <ul>
     *   <li>不要在令牌中包含敏感信息</li>
     *   <li>确保令牌的过期时间适当</li>
     *   <li>在集群环境中确保密钥的一致性</li>
     * </ul>
     *
     * <p>错误处理:</p>
     * <ul>
     *   <li>如果userDetails为null,抛出IllegalArgumentException</li>
     *   <li>如果密钥配置错误,可能抛出SignatureException</li>
     * </ul>
     *
     * @param userDetails 用户详细信息对象,必须非null
     * @return 生成的JWT令牌字符串
     * @throws IllegalArgumentException 如果userDetails为null
     * @throws io.jsonwebtoken.security.SignatureException 如果签名过程失败
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }

    /**
     * 创建JWT令牌。
     *
     * @param claims 令牌声明
     * @param subject 令牌主题
     * @return 创建的JWT令牌字符串
     */
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .claims(claims)
                .subject(subject)
                .issuedAt(new Date(System.currentTimeMillis()))
                .expiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration()))
                .signWith(secretKey)
                .compact();
    }

    /**
     * 从JWT令牌中提取用户名。
     *
     * @param token JWT令牌字符串
     * @return 提取的用户名
     */
    public String extractUsername(String token) {
        return extractAllClaims(token).getSubject();
    }

    /**
     * 从JWT令牌中提取过期时间。
     *
     * @param token JWT令牌字符串
     * @return 提取的过期时间
     */
    public Date extractExpiration(String token) {
        return extractAllClaims(token).getExpiration();
    }

    /**
     * 从JWT令牌中提取所有声明信息
     *
     * <p>解析并验证JWT令牌,提取其中的所有声明(Claims)。这个过程包括:</p>
     * <ul>
     *   <li>验证令牌的签名</li>
     *   <li>检查令牌格式</li>
     *   <li>提取载荷信息</li>
     * </ul>
     *
     * <p>可能的异常情况:</p>
     * <ul>
     *   <li>SignatureException - 令牌签名验证失败</li>
     *   <li>ExpiredJwtException - 令牌已过期</li>
     *   <li>MalformedJwtException - 令牌格式不正确</li>
     *   <li>IllegalArgumentException - 输入为null或空字符串</li>
     * </ul>
     *
     * @param token 要解析的JWT令牌字符串,不能为null
     * @return JWT载荷中包含的所有声明
     * @throws io.jsonwebtoken.JwtException 如果令牌验证或解析失败
     * @throws IllegalArgumentException 如果token为null
     */
    private Claims extractAllClaims(String token) {
        return Jwts.parser()
                .verifyWith(secretKey)
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

    /**
     * 检查JWT令牌是否已过期
     *
     * <p>通过比较令牌中的过期时间(exp claim)与当前系统时间,判断令牌是否已过期。
     * 该方法会调用{@link #extractExpiration}获取过期时间。</p>
     *
     * <p>验证流程:</p>
     * <ol>
     *   <li>提取令牌中的过期时间</li>
     *   <li>获取当前系统时间</li>
     *   <li>比较两个时间</li>
     * </ol>
     *
     * @param token JWT令牌字符串
     * @return true如果令牌已过期,false如果令牌仍然有效
     * @throws io.jsonwebtoken.ExpiredJwtException 如果令牌已过期
     * @throws io.jsonwebtoken.JwtException 如果令牌解析失败
     */
    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    /**
     * 验证JWT令牌的有效性
     *
     * <p>执行完整的令牌验证,包括:</p>
     * <ol>
     *   <li>检查令牌格式和签名</li>
     *   <li>验证令牌是否过期</li>
     *   <li>验证令牌中的用户名与UserDetails匹配</li>
     * </ol>
     *
     * <p>验证失败的情况:</p>
     * <ul>
     *   <li>令牌签名无效</li>
     *   <li>令牌已过期</li>
     *   <li>令牌中的用户名与UserDetails不匹配</li>
     *   <li>令牌格式错误</li>
     * </ul>
     *
     * @param token JWT令牌字符串,不能为null
     * @param userDetails 用户详细信息对象,不能为null
     * @return true如果令牌完全有效,false如果验证失败
     * @throws IllegalArgumentException 如果token或userDetails为null
     * @throws io.jsonwebtoken.JwtException 如果令牌解析过程发生错误
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
}